<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
  <title>PSD图层管理器</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    html, body { height: 100%; background: #2b2b2b; color: #e5e5e5; font-family: "Microsoft YaHei", Arial, sans-serif; }
    
    .app { display: flex; flex-direction: column; height: 100vh; }
    
    /* 顶部标题 */
    .header { background: #1f1f1f; padding: 10px 15px; border-bottom: 1px solid #3a3a3a; display: flex; align-items: center; gap: 10px; }
    .header h1 { font-size: 14px; color: #fff; font-weight: normal; }
    .header input[type="file"] { display: none; }
    .header label[for="psdFile"] { padding: 6px 12px; background: #3a3a3a; border-radius: 4px; cursor: pointer; font-size: 12px; }
    .header label[for="psdFile"]:hover { background: #4a4a4a; }
    
    /* 主内容区 */
    .main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
    
    /* 画布显示区 */
    .preview-area { height: 400px; background: #2b2b2b; display: flex; align-items: center; justify-content: center; padding: 20px; overflow: auto; flex-shrink: 0; }
    canvas.stage { display: block; max-width: 100%; max-height: 100%; background: #1a1a1a; box-shadow: 0 4px 20px rgba(0,0,0,0.5); }
    
    /* 分类管理区 */
    .category-section { background: #333; border-top: 1px solid #444; padding: 12px 15px; flex-shrink: 0; }
    .category-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
    .category-title { font-size: 16px; color: #fff; }
    .export-btn { padding: 8px 20px; background: #ff8c00; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: bold; display: flex; align-items: center; gap: 6px; }
    .export-btn:hover { background: #ff9d1a; }
    .export-btn:disabled { background: #666; cursor: not-allowed; }
    .export-icon { font-size: 16px; }
    
    /* 文件标签栏 */
    .file-tabs { display: flex; gap: 6px; padding: 10px 15px; background: #1f1f1f; border-bottom: 1px solid #444; overflow-x: auto; flex-shrink: 0; }
    .file-tabs::-webkit-scrollbar { height: 4px; }
    .file-tabs::-webkit-scrollbar-track { background: #1a1a1a; }
    .file-tabs::-webkit-scrollbar-thumb { background: #555; border-radius: 2px; }
    .file-tab { padding: 6px 12px; background: #2a2a2a; color: #aaa; border: 1px solid #3a3a3a; border-radius: 6px; cursor: pointer; font-size: 12px; white-space: nowrap; transition: all 0.2s; display: flex; align-items: center; gap: 6px; }
    .file-tab:hover { background: #3a3a3a; border-color: #4a4a4a; }
    .file-tab.active { background: #ff8c00; color: #fff; border-color: #ff8c00; }
    .file-tab-close { margin-left: 4px; font-weight: bold; opacity: 0.6; }
    .file-tab-close:hover { opacity: 1; }
    .empty-file-hint { color: #666; font-size: 12px; padding: 4px 0; }
    
    /* 当前分类名 */
    .current-category { color: #aaa; font-size: 13px; margin-bottom: 8px; }
    
    /* 选择模式切换 */
    .mode-selector { display: flex; gap: 6px; margin-bottom: 10px; align-items: center; }
    .mode-label { color: #ccc; font-size: 12px; margin-right: 4px; }
    .mode-btn { padding: 5px 12px; background: #444; color: #ccc; border: 1px solid #555; border-radius: 5px; cursor: pointer; font-size: 11px; transition: all 0.2s; }
    .mode-btn:hover { background: #555; }
    .mode-btn.active { background: #ff8c00; color: #fff; border-color: #ff8c00; }
    
    /* 标签栏 */
    .tags-row { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
    .tag-btn { padding: 6px 14px; background: #444; color: #ccc; border: 1px solid #555; border-radius: 6px; cursor: pointer; font-size: 12px; transition: all 0.2s; }
    .tag-btn:hover { background: #555; border-color: #666; }
    .tag-btn.active { background: #ff8c00; color: #fff; border-color: #ff8c00; }
    
    /* 图层缩略图区 */
    .thumbnails-section { background: #2b2b2b; padding: 12px 15px; border-top: 1px solid #444; height: 250px; overflow-y: auto; overflow-x: hidden; flex-shrink: 0; }
    .thumbnails-grid { display: flex; gap: 10px; flex-wrap: wrap; align-content: flex-start; }
    .thumb-item { position: relative; flex-shrink: 0; cursor: pointer; }
    .thumb-wrapper { width: 80px; height: 100px; background: #1a1a1a; border: 2px solid #444; border-radius: 8px; overflow: hidden; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
    .thumb-wrapper:hover { border-color: #666; transform: scale(1.05); }
    .thumb-wrapper.active { border-color: #ff8c00; }
    .thumb-img { max-width: 100%; max-height: 100%; object-fit: contain; }
    .thumb-label { text-align: center; font-size: 11px; color: #aaa; margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 80px; }
    
    /* 滚动条样式 */
    .thumbnails-section::-webkit-scrollbar { height: 8px; }
    .thumbnails-section::-webkit-scrollbar-track { background: #1a1a1a; }
    .thumbnails-section::-webkit-scrollbar-thumb { background: #555; border-radius: 4px; }
    .thumbnails-section::-webkit-scrollbar-thumb:hover { background: #666; }
    
    /* 空状态提示 */
    .empty-state { text-align: center; padding: 40px; color: #666; }
    .empty-state-icon { font-size: 48px; margin-bottom: 10px; }
    .empty-state-text { font-size: 14px; }
  </style>
</head>
<body>
  <div class="app">
    <!-- 顶部标题栏 -->
    <div class="header">
      <h1>PSD图层管理器</h1>
      <label for="psdFile">选择PSD文件</label>
      <input id="psdFile" type="file" accept=".psd" multiple />
    </div>

    <!-- 主内容区 -->
    <div class="main-content">
      <!-- 文件标签栏 -->
      <div class="file-tabs" id="fileTabs">
        <div class="empty-file-hint">请选择PSD文件...</div>
      </div>

      <!-- 画布预览区 -->
      <div class="preview-area">
        <canvas id="stage" class="stage" width="600" height="800"></canvas>
      </div>

      <!-- 分类管理区 -->
      <div class="category-section">
        <div class="category-header">
          <div class="category-title">分类管理</div>
          <button id="exportPng" class="export-btn" disabled>
            <span class="export-icon">📤</span>
            <span>导出</span>
          </button>
        </div>
        
        <div class="current-category" id="currentCategory">不良少年.psd</div>
        
        <!-- 选择模式切换 -->
        <div class="mode-selector">
          <span class="mode-label">图层选择：</span>
          <button class="mode-btn active" id="modeSingle">单选</button>
          <button class="mode-btn" id="modeMultiple">多选</button>
        </div>
        
        <!-- 分类标签 -->
        <div class="tags-row" id="categoryTags">
          <!-- 分类标签将从PSD文件自动生成 -->
        </div>
      </div>

      <!-- 图层缩略图区 -->
      <div class="thumbnails-section">
        <div class="thumbnails-grid" id="thumbnailsGrid">
          <!-- 缩略图将动态生成 -->
        </div>
      </div>
    </div>
  </div>

  <!-- ag-psd 浏览器打包版（全局变量 window.agPsd） -->
  <script src="https://cdn.jsdelivr.net/npm/ag-psd@28.4.1/dist/bundle.js"></script>
  <!-- 可选后备源（若上面失败，可启用下一行）： -->
  <!-- <script src="https://unpkg.com/ag-psd@28.4.1/dist/bundle.js"></script> -->

  <script>
    // 安全获取全局 agPsd
    function getAgPsd() {
      if (window.agPsd) return window.agPsd;
      alert('未检测到 ag-psd（window.agPsd）。请检查 CDN 是否可访问。');
      throw new Error('ag-psd not available');
    }

    const state = {
      psdFiles: [],           // 存储所有加载的PSD文件数据：[{id, name, psd, flatLayers, layerGroups, categories}]
      currentFileId: null,    // 当前显示的文件ID
      currentCategory: null,  // 当前选中的分类
      activeLayerIndex: -1,   // 当前激活的图层
      selectionMode: 'single' // 图层选择模式：'single' 单选, 'multiple' 多选（默认单选）
    };

    // 获取当前文件数据
    function getCurrentFile() {
      return state.psdFiles.find(f => f.id === state.currentFileId) || null;
    }

    const BLEND_MAP = {
      'normal': 'source-over',
      'multiply': 'multiply',
      'screen': 'screen',
      'overlay': 'overlay',
      'darken': 'darken',
      'lighten': 'lighten',
      'color-dodge': 'color-dodge',
      'color-burn': 'color-burn',
      'hard-light': 'hard-light',
      'soft-light': 'soft-light',
      'difference': 'difference',
      'exclusion': 'exclusion',
      'hue': 'hue',
      'saturation': 'saturation',
      'color': 'color',
      'luminosity': 'luminosity'
    };

    const $ = (sel) => document.querySelector(sel);
    const stage = $('#stage');
    const ctx = stage.getContext('2d');

    // 模式切换按钮事件
    $('#modeSingle').addEventListener('click', () => {
      state.selectionMode = 'single';
      $('#modeSingle').classList.add('active');
      $('#modeMultiple').classList.remove('active');
    });

    $('#modeMultiple').addEventListener('click', () => {
      state.selectionMode = 'multiple';
      $('#modeMultiple').classList.add('active');
      $('#modeSingle').classList.remove('active');
    });

    // 辅助函数：递归打印PSD结构
    function printPsdStructure(nodes, depth = 0) {
      if (!nodes) return;
      nodes.forEach((node, index) => {
        const indent = '  '.repeat(depth);
        const type = node.children ? '📁' : '🖼️';
        const visibleStatus = node.hidden ? '❌隐藏' : '✅显示';
        console.log(`${indent}${type} ${node.name || '未命名'} - ${visibleStatus}`);
        
        if (node.children) {
          printPsdStructure(node.children, depth + 1);
        }
      });
    }

    // 读取 PSD 文件（支持多文件）
    $('#psdFile').addEventListener('change', async (e) => {
      const files = e.target.files;
      if (!files || files.length === 0) return;
      
      // 加载所有选中的文件
      for (let i = 0; i < files.length; i++) {
        await loadPsdFile(files[i]);
      }
      
      // 清空文件输入，允许重复选择相同文件
      e.target.value = '';
    });

    // 加载单个PSD文件
    async function loadPsdFile(file) {
      const buf = await file.arrayBuffer();
      const { readPsd } = getAgPsd();
      
      // 读取PSD文件（包含图层数据和可见性信息）
      const psd = readPsd(buf, { 
        skipLayerImageData: false, 
        skipCompositeImageData: false, 
        skipThumbnail: true
      });
      
      // 控制台输出PSD文件信息
      console.log('========== PSD文件加载 ==========');
      console.log('📄 文件名:', file.name);
      console.log('📐 尺寸:', `${psd.width} × ${psd.height}`);
      console.log('\n🗂️ 图层结构:');
      printPsdStructure(psd.children);
      console.log('================================');
      
      // 扁平化图层并提取图层组
      const result = flattenLayersWithGroups(psd.children || []);
      
      // 根据图层组自动分类
      const categories = categorizeByGroupsForFile(result.groups);
      
      // 创建文件数据对象
      const fileId = `file_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
      const fileData = {
        id: fileId,
        name: file.name,
        psd: psd,
        flatLayers: result.layers,
        layerGroups: result.groups,
        categories: categories,
        currentCategory: null
      };
      
      // 设置默认选中分类
      const cats = Object.keys(categories);
      fileData.currentCategory = cats.find(cat => cat !== '未分组') || cats[0] || null;
      
      // 添加到文件列表
      state.psdFiles.push(fileData);
      
      // 输出最终处理结果
      const visibleCount = result.layers.filter(l => l.visible).length;
      const hiddenCount = result.layers.filter(l => !l.visible).length;
      console.log('\n========== 加载完成 ==========');
      console.log(`📊 图层统计: 总计 ${result.layers.length} | ✅显示 ${visibleCount} | ❌隐藏 ${hiddenCount}`);
      console.log(`📁 分类数量: ${Object.keys(categories).length}`);
      console.log('================================\n');
      
      // 切换到新加载的文件
      switchToFile(fileId);
      
      // 渲染文件标签栏
      renderFileTabs();
    }

    // 独立的分类函数（不使用state）
    function categorizeByGroupsForFile(groups) {
      const categories = {};
      groups.forEach(group => {
        if (group.layerIndices.length > 0) {
          categories[group.name] = group.layerIndices;
        }
      });
      return categories;
    }

    // 扁平化图层并提取图层组信息
    function flattenLayersWithGroups(children) {
      const layers = [];
      const groups = [];
      const groupMap = new Map(); // 用于记录每个图层属于哪个组
      
      const visit = (node, depth, currentGroup, parentVisible = true) => {
        // 读取节点的可见性
        // ag-psd 可能使用 hidden 属性（true=隐藏）或 visible 属性（true=显示）
        let nodeVisible;
        if (node.hidden !== undefined) {
          // 如果有 hidden 属性，hidden=true 表示隐藏，hidden=false 表示可见
          nodeVisible = !node.hidden;
        } else if (node.visible !== undefined) {
          // 如果有 visible 属性，visible=true 表示可见
          nodeVisible = node.visible;
        } else {
          // 都没有则默认可见
          nodeVisible = true;
        }
        
        const actualVisible = parentVisible && nodeVisible;
        
        // 调试日志（可选，生产环境可以注释掉）
        // if (node.name) {
        //   console.log(`${node.name}: hidden=${node.hidden}, nodeVisible=${nodeVisible}, actualVisible=${actualVisible}`);
        // }
        
        const opacity = (typeof node.opacity === 'number') ? node.opacity : 1;
        const blend = (node.blendMode || 'normal').toLowerCase();
        const left = (typeof node.left === 'number' ? node.left : 0);
        const top = (typeof node.top === 'number' ? node.top : 0);
        
        // 如果这个节点是一个组（有children但自己没有canvas）
        if (Array.isArray(node.children) && node.children.length > 0) {
          const groupName = node.name || `未命名组${groups.length + 1}`;
          const groupInfo = {
            name: groupName,
            node: node,
            visible: nodeVisible,
            layerIndices: []
          };
          groups.push(groupInfo);
          
          // 递归处理组内的图层，传递父级可见性
          node.children.forEach(child => visit(child, depth + 1, groupInfo, actualVisible));
        } 
        // 如果这个节点是一个图层（有canvas）
        else if (node.canvas) {
          const layerIndex = layers.length;
          const entryBase = { 
            node, 
            depth, 
            visible: actualVisible,  // 使用计算后的实际可见性
            opacity, 
            blend,
            left, 
            top, 
            width: node.canvas.width, 
            height: node.canvas.height,
            groupName: currentGroup ? currentGroup.name : '未分组'
          };
          
          layers.push(entryBase);
          
          // 记录这个图层属于哪个组
          if (currentGroup) {
            currentGroup.layerIndices.push(layerIndex);
          } else {
            // 没有组的图层归入"未分组"
            let ungrouped = groups.find(g => g.name === '未分组');
            if (!ungrouped) {
              ungrouped = { name: '未分组', node: null, layerIndices: [] };
              groups.push(ungrouped);
            }
            ungrouped.layerIndices.push(layerIndex);
          }
        }
      };
      
      children.forEach(ch => visit(ch, 0, null, true));
      return { layers, groups };
    }

    // 切换到指定文件
    function switchToFile(fileId) {
      state.currentFileId = fileId;
      const file = getCurrentFile();
      if (!file) return;
      
      // 更新当前分类
      state.currentCategory = file.currentCategory;
      
      // 更新显示
      $('#currentCategory').textContent = file.name;
      resizeStage(file.psd.width, file.psd.height);
      renderCategoryTags();
      renderThumbnails();
      draw();
      $('#exportPng').disabled = false;
      
      // 更新文件标签激活状态
      updateFileTabsActive();
    }

    // 渲染文件标签栏
    function renderFileTabs() {
      const tabsContainer = $('#fileTabs');
      tabsContainer.innerHTML = '';
      
      if (state.psdFiles.length === 0) {
        tabsContainer.innerHTML = '<div class="empty-file-hint">请选择PSD文件...</div>';
        return;
      }
      
      state.psdFiles.forEach(file => {
        const tab = document.createElement('div');
        tab.className = 'file-tab';
        tab.dataset.fileId = file.id;
        
        if (file.id === state.currentFileId) {
          tab.classList.add('active');
        }
        
        const nameSpan = document.createElement('span');
        nameSpan.textContent = file.name;
        
        const closeBtn = document.createElement('span');
        closeBtn.className = 'file-tab-close';
        closeBtn.textContent = '×';
        closeBtn.addEventListener('click', (e) => {
          e.stopPropagation();
          closeFile(file.id);
        });
        
        tab.appendChild(nameSpan);
        tab.appendChild(closeBtn);
        
        tab.addEventListener('click', () => {
          switchToFile(file.id);
        });
        
        tabsContainer.appendChild(tab);
      });
    }

    // 更新文件标签激活状态
    function updateFileTabsActive() {
      const tabs = $('#fileTabs').querySelectorAll('.file-tab');
      tabs.forEach(tab => {
        if (tab.dataset.fileId === state.currentFileId) {
          tab.classList.add('active');
        } else {
          tab.classList.remove('active');
        }
      });
    }

    // 关闭文件
    function closeFile(fileId) {
      const index = state.psdFiles.findIndex(f => f.id === fileId);
      if (index === -1) return;
      
      state.psdFiles.splice(index, 1);
      
      // 如果关闭的是当前文件，切换到其他文件
      if (state.currentFileId === fileId) {
        if (state.psdFiles.length > 0) {
          // 切换到前一个或后一个文件
          const newIndex = Math.max(0, index - 1);
          switchToFile(state.psdFiles[newIndex].id);
        } else {
          // 没有文件了，清空显示
          state.currentFileId = null;
          state.currentCategory = null;
          $('#currentCategory').textContent = '';
          $('#categoryTags').innerHTML = '';
          $('#thumbnailsGrid').innerHTML = '';
          $('#exportPng').disabled = true;
          const ctx = $('#stage').getContext('2d');
          ctx.clearRect(0, 0, $('#stage').width, $('#stage').height);
        }
      }
      
      renderFileTabs();
    }

    // 渲染分类标签
    function renderCategoryTags() {
      const tagsContainer = $('#categoryTags');
      tagsContainer.innerHTML = '';
      
      const file = getCurrentFile();
      if (!file) return;
      
      // 获取所有分类并排序，将"未分组"放到最后
      const categories = Object.keys(file.categories).sort((a, b) => {
        if (a === '未分组') return 1;
        if (b === '未分组') return -1;
        return 0;
      });
      
      for (const category of categories) {
        const btn = document.createElement('button');
        btn.className = 'tag-btn';
        btn.textContent = category;
        
        if (category === state.currentCategory) {
          btn.classList.add('active');
        }
        
        btn.addEventListener('click', () => {
          state.currentCategory = category;
          file.currentCategory = category;
          updateCategoryTags();
          renderThumbnails();
        });
        
        tagsContainer.appendChild(btn);
      }
    }

    // 更新分类标签激活状态
    function updateCategoryTags() {
      const buttons = $('#categoryTags').querySelectorAll('.tag-btn');
      buttons.forEach(btn => {
        if (btn.textContent === state.currentCategory) {
          btn.classList.add('active');
        } else {
          btn.classList.remove('active');
        }
      });
    }

    // 渲染缩略图
    function renderThumbnails() {
      const grid = $('#thumbnailsGrid');
      grid.innerHTML = '';
      
      const file = getCurrentFile();
      if (!file) return;
      
      if (!state.currentCategory || !file.categories[state.currentCategory]) {
        grid.innerHTML = '<div class="empty-state"><div class="empty-state-text">该分类暂无图层</div></div>';
        return;
      }
      
      const layerIndices = file.categories[state.currentCategory];
      
      layerIndices.forEach((layerIndex, displayIndex) => {
        const layer = file.flatLayers[layerIndex];
        if (!layer || !layer.node.canvas) return;
        
        const thumbItem = document.createElement('div');
        thumbItem.className = 'thumb-item';
        thumbItem.dataset.layerIndex = layerIndex;
        
        // 生成缩略图
        const thumbCanvas = document.createElement('canvas');
        const maxSize = 80;
        const scale = Math.min(maxSize / layer.width, maxSize / layer.height, 1);
        thumbCanvas.width = layer.width * scale;
        thumbCanvas.height = layer.height * scale;
        const thumbCtx = thumbCanvas.getContext('2d');
        thumbCtx.drawImage(layer.node.canvas, 0, 0, thumbCanvas.width, thumbCanvas.height);
        
        const wrapper = document.createElement('div');
        wrapper.className = 'thumb-wrapper';
        // 图层可见时显示橙色边框，隐藏时显示灰色边框
        if (layer.visible) {
          wrapper.classList.add('active');
        }
        
        const img = document.createElement('img');
        img.className = 'thumb-img';
        img.src = thumbCanvas.toDataURL();
        
        const label = document.createElement('div');
        label.className = 'thumb-label';
        label.textContent = layer.node.name || `图层${layerIndex}`;
        
        wrapper.appendChild(img);
        thumbItem.appendChild(wrapper);
        thumbItem.appendChild(label);
        
        // 点击缩略图切换显示/隐藏
        thumbItem.addEventListener('click', () => {
          toggleLayerVisibility(layerIndex);
          renderThumbnails();
          draw();
        });
        
        grid.appendChild(thumbItem);
      });
    }

    // 切换图层可见性
    function toggleLayerVisibility(index) {
      const file = getCurrentFile();
      if (!file) return;
      
      const layer = file.flatLayers[index];
      if (!layer) return;
      
      const targetGroupName = layer.groupName;
      
      // 单选模式：同一分组下只能显示一个图层
      if (state.selectionMode === 'single') {
        // 如果点击的是已显示的图层，则隐藏它
        if (layer.visible) {
          layer.visible = false;
          if (layer.node) layer.node.visible = false;
        } else {
          // 先隐藏同组的所有图层
          const layerIndicesInGroup = file.categories[targetGroupName] || [];
          layerIndicesInGroup.forEach(idx => {
            const l = file.flatLayers[idx];
            if (l) {
              l.visible = false;
              if (l.node) l.node.visible = false;
            }
          });
          
          // 再显示当前点击的图层
          layer.visible = true;
          if (layer.node) layer.node.visible = true;
        }
      } 
      // 多选模式：可以同时显示多个图层
      else {
        layer.visible = !layer.visible;
        if (layer.node) {
          layer.node.visible = layer.visible;
        }
      }
      
      state.activeLayerIndex = index;
    }

    // 绘制画布
    function draw() {
      const file = getCurrentFile();
      if (!file) return;
      
      const { width, height } = file.psd;
      ctx.clearRect(0, 0, stage.width, stage.height);
      
      // 绘制透明背景
      drawCheckerboard(ctx, width, height);
      
      // 绘制所有可见图层
      file.flatLayers.forEach((layer) => {
        if (!layer.visible || !layer.node.canvas) return;
        
        ctx.globalAlpha = layer.opacity ?? 1;
        ctx.globalCompositeOperation = BLEND_MAP[layer.blend] || 'source-over';
        ctx.drawImage(layer.node.canvas, layer.left, layer.top);
      });
      
      ctx.globalAlpha = 1;
      ctx.globalCompositeOperation = 'source-over';
    }

    // 绘制棋盘背景
    function drawCheckerboard(ctx, w, h, size = 16) {
      const cols = Math.ceil(w / size);
      const rows = Math.ceil(h / size);
      
      for (let y = 0; y < rows; y++) {
        for (let x = 0; x < cols; x++) {
          ctx.fillStyle = (x + y) % 2 ? '#2a2a2a' : '#1a1a1a';
          ctx.fillRect(x * size, y * size, size, size);
        }
      }
    }

    // 调整画布尺寸
    function resizeStage(w, h) {
      stage.width = Math.max(64, w);
      stage.height = Math.max(64, h);
    }

    // 导出 PNG
    $('#exportPng').addEventListener('click', () => {
      const file = getCurrentFile();
      if (!file) return;
      
      const w = file.psd.width;
      const h = file.psd.height;
      const offCanvas = document.createElement('canvas');
      offCanvas.width = w;
      offCanvas.height = h;
      const offCtx = offCanvas.getContext('2d');
      
      // 绘制所有可见图层
      file.flatLayers.forEach((layer) => {
        if (!layer.visible || !layer.node.canvas) return;
        
        offCtx.globalAlpha = layer.opacity ?? 1;
        offCtx.globalCompositeOperation = BLEND_MAP[layer.blend] || 'source-over';
        offCtx.drawImage(layer.node.canvas, layer.left, layer.top);
      });
      
      const url = offCanvas.toDataURL('image/png');
      const a = document.createElement('a');
      a.href = url;
      const fileName = file.name.replace('.psd', '') || 'export';
      a.download = `${fileName}_${Date.now()}.png`;
      a.click();
    });
  </script>
</body>
</html>